# 页面组件
import base64
import json
from io import BytesIO
from time import sleep
import PyPDF2
import pandas as pd
import pyvista as pv
import streamlit as st
from PIL import Image
from code_editor import code_editor
from stpyvista import stpyvista
from stqdm import stqdm
from streamlit_cropper import st_cropper
from streamlit_echarts import st_echarts
from streamlit_option_menu import option_menu


# 侧边栏和主页搭建
def layout_sidebar(image, title, header, subheader, choices, autor, date):
    # 定义浏览器页面作为：运行控制区域、信息可视化展示区
    st.sidebar.image(image, width=120)
    st.sidebar.title(title)
    st.sidebar.header(header + '：', divider='rainbow')
    st.subheader(subheader + ':', divider='rainbow')
    my_choice = st.sidebar.radio('点击按钮，选择一个运行项：', choices)
    st.sidebar.divider()
    st.sidebar.text('编程:' + autor)
    st.sidebar.text('日期:' + date)
    return my_choice


def menu_setting(options, icons=None):
    if icons is None:
        icons = []
        for i in range(len(options)):
            icons.append("list-task")
    selected2 = option_menu(None, options,
                            icons=icons,
                            menu_icon="cast", default_index=0, orientation="horizontal")
    return selected2


def fig_to_image(fig):
    # 将matplotlib图形转换为图像数据
    from io import BytesIO
    buf = BytesIO()
    fig.savefig(buf, format='png')
    buf.seek(0)
    return buf


# 使用echarts画图，单折线图
def basic_line(title, x_data, y_data):
    if not isinstance(x_data, list) or not isinstance(y_data, list) or len(x_data) != len(y_data):
        raise ValueError("x_data和y_data必须是数组且长度相等")
    option = {
        "title": {"text": title},
        "xAxis": {
            "type": "category",
            "data": x_data,
        },
        "yAxis": {"type": "value"},
        "series": [{"data": y_data, "type": "line"}],
    }
    st_echarts(
        options=option, height="400px",
    )


# echarts画堆叠折线图，输入标题，图注，横纵坐标数据
def stacked_line(title, legend, x_data, y_datas):
    if not isinstance(x_data, list) or not isinstance(y_datas, list) or len(legend) != len(y_datas):
        raise ValueError("x_data和y_datas必须是数组且长度相等")
    series = []
    for name, data in zip(legend, y_datas):
        series.append({
            "name": name,
            "type": "line",
            "stack": "总量",
            "data": data,
        })
    option = {
        "title": {"text": title},
        "legend": {"data": legend},
        "grid": {"left": "3%", "right": "4%", "bottom": "3%", "containLabel": True},
        "toolbox": {"feature": {"saveAsImage": {}}},
        "xAxis": {
            "type": "category",
            "boundaryGap": False,
            "data": x_data,
        },
        "yAxis": {"type": "value"},
        "series": series,
    }
    st_echarts(
        options=option, height="400px",
    )


# echarts画基础柱状图
def basic_bar(title, x_data, y_data):
    if not isinstance(x_data, list) or not isinstance(y_data, list) or len(x_data) != len(y_data):
        raise ValueError("x_data和y_data必须是数组且长度相等")
    options = {
        "title": {"text": title},
        "xAxis": {
            "type": "category",
            "data": x_data,
        },
        "yAxis": {"type": "value"},
        "series": [{"data": y_data, "type": "bar"}],
    }
    st_echarts(options=options, height="500px")


# 堆叠柱状图
def stack_bar(title, legend, x_data, y_datas):
    if not isinstance(x_data, list) or not isinstance(y_datas, list) or len(legend) != len(y_datas):
        raise ValueError("x_data和y_datas必须是数组且长度相等")
    series = []
    for name, data in zip(legend, y_datas):
        series.append({
            "name": name,
            "type": "bar",
            "stack": "total",
            "emphasis": {"focus": "series"},
            "data": data,
        })
    options = {
        "title": {"text": title},
        "tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}},
        "legend": {
            "data": legend
        },
        "grid": {"left": "3%", "right": "4%", "bottom": "3%", "containLabel": True},
        "xAxis": {"type": "value"},
        "yAxis": {
            "type": "category",
            "data": x_data,
        },
        "series": series
    }
    st_echarts(options=options, height="500px")


# 基础pie图
def simple_pie(title, subtitle, legend, data):
    if not isinstance(legend, list) or not isinstance(data, list) or len(legend) != len(data):
        raise ValueError("legend和data必须是数组且长度相等")
    in_data = []
    for name, data in zip(legend, data):
        in_data.append({
            "value": data,
            "name": name,
        })
    options = {
        "title": {"text": title, "subtext": subtitle, "left": "center"},
        "tooltip": {"trigger": "item"},
        "legend": {"orient": "vertical", "left": "left"},
        "series": [
            {
                "name": "值",
                "type": "pie",
                "radius": "50%",
                "data": in_data,
                "emphasis": {
                    "itemStyle": {
                        "shadowBlur": 10,
                        "shadowOffsetX": 0,
                        "shadowColor": "rgba(0, 0, 0, 0.5)",
                    }
                },
            }
        ],
    }
    st_echarts(
        options=options, height="600px",
    )


# 基础箱盒图
def basic_boxplot(title, y_label, datas):
    option = {
        "title": [
            {"text": title, "left": "center"},
            {
                "text": "upper: Q3 + 1.5 * IQR \nlower: Q1 - 1.5 * IQR",
                "borderColor": "#999",
                "borderWidth": 1,
                "textStyle": {"fontWeight": "normal", "fontSize": 14, "lineHeight": 20},
                "left": "10%",
                "top": "90%",
            },
        ],
        "dataset": [
            {
                "source": datas,
            },
            {
                "transform": {
                    "type": "boxplot",
                    "config": {"itemNameFormatter": "No.{value}"},
                }
            },
            {"fromDatasetIndex": 1, "fromTransformResult": 1},
        ],
        "tooltip": {"trigger": "item", "axisPointer": {"type": "shadow"}},
        "grid": {"left": "10%", "right": "10%", "bottom": "15%"},
        "xAxis": {
            "type": "category",
            "boundaryGap": True,
            "nameGap": 30,
            "splitArea": {"show": False},
            "splitLine": {"show": False},
        },
        "yAxis": {
            "type": "value",
            "name": y_label,
            "splitArea": {"show": True},
        },
        "series": [
            {"name": "boxplot", "type": "boxplot", "datasetIndex": 1},
            {"name": "outlier", "type": "scatter", "datasetIndex": 2},
        ],
    }
    st_echarts(option, height="500px")


# 基础雷达图
def basic_radar(title, legend, radar, datas):
    indicator = []
    for name, max_value in zip(radar, datas[0]):
        indicator.append({
            "name": name,
            "max": max_value,
        })
    in_data = []
    for i in range(len(legend)):
        in_data.append({
            "value": datas[i + 1],
            "name": legend[i],
        })
    option = {
        "title": {"text": title},
        "legend": {"data": legend},
        "radar": {
            "indicator": indicator,
        },
        "series": [
            {
                "type": "radar",
                "data": in_data,
            }
        ],
    }
    st_echarts(option, height="500px")


# 代码展示
def code_show(input_code=None):
    with open(input_code) as python_file:
        code_detail = python_file.read()
    height = [19, 22]
    mode_list = ["css", "html", "java", "javascript", "json", "json5", "latex", "less", "lua", "markdown", "mask",
                 "matlab", "mysql", "nginx", "pgsql", "php", "python", "rust", "sass", "scss", "sql", "sqlserver",
                 "svg", "text", "textile", "vbscript", "xml", "xquery", "yaml", ]
    lang = "python"
    theme = "default"
    with st.expander("Settings", expanded=True):
        col1, col2 = st.columns(2)
        lang = col1.selectbox("lang:", mode_list, index=mode_list.index("python"))
        theme = col2.selectbox("theme:", ["default", "light", "dark", "contrast"])

    st.write("### Output:")
    ace_props = {"style": {"borderRadius": "0px 0px 8px 8px"}}
    response_dict = code_editor(code_detail, height=height, lang=lang, theme=theme, props=ace_props,
                                allow_reset=True, key="code_editor")
    if len(response_dict['id']) != 0 and (response_dict['type'] == "submit" or response_dict['type'] == "selection"):
        st.write(response_dict)


# 知识图谱
def draw_graph(title, input_json):
    with open(input_json, "r") as f:
        graph = json.loads(f.read())

    # for idx, _ in enumerate(graph["nodes"]):
    #     graph["nodes"][idx]["symbolSize"] = 5

    option = {
        "title": {
            "text": title,
            "top": "bottom",
            "left": "right",
        },
        "tooltip": {},
        "legend": [{"data": [a["name"] for a in graph["categories"]]}],
        "series": [
            {
                "name": title,
                "type": "graph",
                "layout": "force",
                "data": graph["nodes"],
                "links": graph["links"],
                "categories": graph["categories"],
                "roam": True,
                "label": {"position": "right"},
                "draggable": True,
                "force": {"repulsion": 100},
            }
        ],
    }
    st_echarts(option, height="500px")


# 3D模型展示
def show_3D(path):
    plotter = pv.Plotter(window_size=[400, 400])
    # 创建一个PyVista对象，加载OBJ文件
    obj_path = path  # 替换为你的OBJ文件路径
    mesh = pv.read(obj_path)
    x, y, z = mesh.cell_centers().points.T
    mesh["My scalar"] = z
    plotter.add_mesh(
        mesh,
        scalars="My scalar",
        cmap="prism",
        show_edges=True,
        edge_color="#001100",
        ambient=0.2,
    )
    plotter.background_color = "white"
    plotter.view_isometric()
    stpyvista(plotter)


# 图片查看和裁剪
def image_cropping(title):
    st.header(title)
    col1, col2 = st.columns([3, 1])
    img_file = col1.file_uploader(label='Upload a file', type=['png', 'jpg'])
    box_color = col2.color_picker(label="Box Color", value='#0000FF')
    realtime_update = st.checkbox(label="Update in Real Time", value=True)

    aspect_choice = st.radio(label="Aspect Ratio", options=["1:1", "16:9", "4:3", "2:3", "Free"], horizontal=True)
    aspect_dict = {
        "1:1": (1, 1),
        "16:9": (16, 9),
        "4:3": (4, 3),
        "2:3": (2, 3),
        "Free": None
    }
    aspect_ratio = aspect_dict[aspect_choice]

    if img_file:
        img = Image.open(img_file)
        if not realtime_update:
            st.write("Double click to save crop")
        # Get a cropped image from the frontend
        cropped_img = st_cropper(img, realtime_update=realtime_update, box_color=box_color,
                                 aspect_ratio=aspect_ratio)

        # Manipulate cropped image at will
        st.write("Preview")
        _ = cropped_img.thumbnail((150, 150))
        st.image(cropped_img)


# 表格展示，针对第一行是标签的数据
def draw_table(title, path):
    st.title(title)
    df = pd.read_excel(path)
    labels = df.iloc[0].tolist()

    # 构建数据字典
    data = {}
    for col_index, label in enumerate(labels):
        data[label] = df.iloc[1:, col_index]
    # 先转化为标准格式，再转化为markdown格式
    dff = pd.DataFrame(data)
    markdown_data = dff.to_markdown(index=True)
    st.markdown(markdown_data, unsafe_allow_html=True)


# PDF查阅
def view_pdf(pdf_file):
    pdf = PyPDF2.PdfReader(pdf_file)  # 读取PDF文件
    total_pages_n = len(pdf.pages)  # 设置一个slider用于该pdf文件翻页，总页数时total_papers_num！
    current_page_n = st.slider("选择当前pdf文件的页码", 1, total_pages_n, 1) - 1  # # 添加一个滑块用于选择页码，减1是因为页码从0开始
    # 显示pdf文件的当前页码内容
    pdf_writer = PyPDF2.PdfWriter()
    pdf_writer.add_page(pdf.pages[current_page_n])
    pdf_bytes = BytesIO()
    pdf_writer.write(pdf_bytes)
    pdf_bytes.seek(0)
    #
    base64_pdf = base64.b64encode(pdf_bytes.read()).decode('utf-8')
    pdf_display = f'<embed src="data:application/pdf;base64,{base64_pdf}" width="675" height="900" type="application/pdf">'
    st.markdown(pdf_display, unsafe_allow_html=True)


# 进度条
def progress_bar(message, total_time, sleep_time):
    for _ in stqdm(range(total_time), desc=message, mininterval=1):
        sleep(sleep_time)
